
#include <XEngine/TgaImg.h>

namespace XEngine
{
    TGAImg::TGAImg()
    { 
        pImage=pPalette=pData=NULL;
        iWidth=iHeight=iBPP=bEnc=0;
        lImageSize=0;
    }


    TGAImg::~TGAImg()
    {
        if(pImage)
        {
            delete [] pImage;
            pImage=NULL;
        }

        if(pPalette)
        {
            delete [] pPalette;
            pPalette=NULL;
        }

        if(pData)
        {
            delete [] pData;
            pData=NULL;
        }
    }


    int TGAImg::Load(char* szFilename)
    {
        using namespace std;
        ifstream fIn;
        unsigned long ulSize;
        int iRet;

        // Clear out any existing image and palette
        if(pImage)
        {
            delete [] pImage;
            pImage=NULL;
        }

        if(pPalette)
        {
            delete [] pPalette;
            pPalette=NULL;
        }

        // Open the specified file
        fIn.open(szFilename,ios::binary);

        if(fIn==NULL)
            return IMG_ERR_NO_FILE;

        // Get file size
        fIn.seekg(0,ios_base::end);
        ulSize=fIn.tellg();
        fIn.seekg(0,ios_base::beg);

        // Allocate some space
        // Check and clear pDat, just in case
        if(pData)
            delete [] pData; 

        pData=new unsigned char[ulSize];

        if(pData==NULL)
        {
            fIn.close();
            return IMG_ERR_MEM_FAIL;
        }

        // Read the file into memory
        fIn.read((char*)pData,ulSize);

        fIn.close();

        // Process the header
        iRet=ReadHeader();

        if(iRet!=IMG_OK)
            return iRet;

        switch(bEnc)
        {
        case 1: // Raw Indexed
            {
                // Check filesize against header values
                if((lImageSize+18+pData[0]+768)>ulSize)
                    return IMG_ERR_BAD_FORMAT;

                // Double check image type field
                if(pData[1]!=1)
                    return IMG_ERR_BAD_FORMAT;

                // Load image data
                iRet=LoadRawData();
                if(iRet!=IMG_OK)
                    return iRet;

                // Load palette
                iRet=LoadTgaPalette();
                if(iRet!=IMG_OK)
                    return iRet;

                break;
            }

        case 2: // Raw RGB
            {
                // Check filesize against header values
                if((lImageSize+18+pData[0])>ulSize)
                    return IMG_ERR_BAD_FORMAT;

                // Double check image type field
                if(pData[1]!=0)
                    return IMG_ERR_BAD_FORMAT;

                // Load image data
                iRet=LoadRawData();
                if(iRet!=IMG_OK)
                    return iRet;

                BGRtoRGB(); // Convert to RGB
                break;
            }

        case 9: // RLE Indexed
            {
                // Double check image type field
                if(pData[1]!=1)
                    return IMG_ERR_BAD_FORMAT;

                // Load image data
                iRet=LoadTgaRLEData();
                if(iRet!=IMG_OK)
                    return iRet;

                // Load palette
                iRet=LoadTgaPalette();
                if(iRet!=IMG_OK)
                    return iRet;

                break;
            }

        case 10: // RLE RGB
            {
                // Double check image type field
                if(pData[1]!=0)
                    return IMG_ERR_BAD_FORMAT;

                // Load image data
                iRet=LoadTgaRLEData();
                if(iRet!=IMG_OK)
                    return iRet;

                BGRtoRGB(); // Convert to RGB
                break;
            }

        default:
            return IMG_ERR_UNSUPPORTED;
        }

        // Check flip bit
        if((pData[17] & 0x20)==0) 
            FlipImg();

        // Release file memory
        delete [] pData;
        pData=NULL;

        return IMG_OK;
    }


    int TGAImg::ReadHeader() // Examine the header and populate our class attributes
    {
        short ColMapStart,ColMapLen;
        short x1,y1,x2,y2;

        if(pData==NULL)
            return IMG_ERR_NO_FILE;

        if(pData[1]>1)    // 0 (RGB) and 1 (Indexed) are the only types we know about
            return IMG_ERR_UNSUPPORTED;

        bEnc=pData[2];     // Encoding flag  1 = Raw indexed image
        //                2 = Raw RGB
        //                3 = Raw greyscale
        //                9 = RLE indexed
        //               10 = RLE RGB
        //               11 = RLE greyscale
        //               32 & 33 Other compression, indexed

        if(bEnc>11)       // We don't want 32 or 33
            return IMG_ERR_UNSUPPORTED;


        // Get palette info
        memcpy(&ColMapStart,&pData[3],2);
        memcpy(&ColMapLen,&pData[5],2);

        // Reject indexed images if not a VGA palette (256 entries with 24 bits per entry)
        if(pData[1]==1) // Indexed
        {
            if(ColMapStart!=0 || ColMapLen!=256 || pData[7]!=24)
                return IMG_ERR_UNSUPPORTED;
        }

        // Get image window and produce width & height values
        memcpy(&x1,&pData[8],2);
        memcpy(&y1,&pData[10],2);
        memcpy(&x2,&pData[12],2);
        memcpy(&y2,&pData[14],2);

        iWidth=(x2-x1);
        iHeight=(y2-y1);

        if(iWidth<1 || iHeight<1)
            return IMG_ERR_BAD_FORMAT;

        // Bits per Pixel
        iBPP=pData[16];

        // Check flip / interleave byte
        if(pData[17]>32) // Interleaved data
            return IMG_ERR_UNSUPPORTED;

        // Calculate image size
        lImageSize=(iWidth * iHeight * (iBPP/8));

        return IMG_OK;
    }


    int TGAImg::LoadRawData() // Load uncompressed image data
    {
        short iOffset;

        if(pImage) // Clear old data if present
            delete [] pImage;

        pImage=new unsigned char[lImageSize];

        if(pImage==NULL)
            return IMG_ERR_MEM_FAIL;

        iOffset=pData[0]+18; // Add header to ident field size

        if(pData[1]==1) // Indexed images
            iOffset+=768;  // Add palette offset

        memcpy(pImage,&pData[iOffset],lImageSize);

        return IMG_OK;
    }


    int TGAImg::LoadTgaRLEData() // Load RLE compressed image data
    {
        short iOffset,iPixelSize;
        unsigned char *pCur;
        unsigned long Index=0;
        unsigned char bLength,bLoop;

        // Calculate offset to image data
        iOffset=pData[0]+18;

        // Add palette offset for indexed images
        if(pData[1]==1)
            iOffset+=768; 

        // Get pixel size in bytes
        iPixelSize=iBPP/8;

        // Set our pointer to the beginning of the image data
        pCur=&pData[iOffset];

        // Allocate space for the image data
        if(pImage!=NULL)
            delete [] pImage;

        pImage=new unsigned char[lImageSize];

        if(pImage==NULL)
            return IMG_ERR_MEM_FAIL;

        // Decode
        while(Index<lImageSize) 
        {
            if(*pCur & 0x80) // Run length chunk (High bit = 1)
            {
                bLength=*pCur-127; // Get run length
                pCur++;            // Move to pixel data  

                // Repeat the next pixel bLength times
                for(bLoop=0;bLoop!=bLength;++bLoop,Index+=iPixelSize)
                    memcpy(&pImage[Index],pCur,iPixelSize);

                pCur+=iPixelSize; // Move to the next descriptor chunk
            }
            else // Raw chunk
            {
                bLength=*pCur+1; // Get run length
                pCur++;          // Move to pixel data

                // Write the next bLength pixels directly
                for(bLoop=0;bLoop!=bLength;++bLoop,Index+=iPixelSize,pCur+=iPixelSize)
                    memcpy(&pImage[Index],pCur,iPixelSize);
            }
        }

        return IMG_OK;
    }


    int TGAImg::LoadTgaPalette() // Load a 256 color palette
    {
        unsigned char bTemp;
        short iIndex,iPalPtr;

        // Delete old palette if present
        if(pPalette)
        {
            delete [] pPalette;
            pPalette=NULL;
        }

        // Create space for new palette
        pPalette=new unsigned char[768];

        if(pPalette==NULL)
            return IMG_ERR_MEM_FAIL;

        // VGA palette is the 768 bytes following the header
        memcpy(pPalette,&pData[pData[0]+18],768);

        // Palette entries are BGR ordered so we have to convert to RGB
        for(iIndex=0,iPalPtr=0;iIndex!=256;++iIndex,iPalPtr+=3)
        {
            bTemp=pPalette[iPalPtr];               // Get Blue value
            pPalette[iPalPtr]=pPalette[iPalPtr+2]; // Copy Red to Blue
            pPalette[iPalPtr+2]=bTemp;             // Replace Blue at the end
        }

        return IMG_OK;
    }


    void TGAImg::BGRtoRGB() // Convert BGR to RGB (or back again)
    {
        unsigned long Index,nPixels;
        unsigned char *bCur;
        unsigned char bTemp;
        short iPixelSize;

        // Set ptr to start of image
        bCur=pImage;

        // Calc number of pixels
        nPixels=iWidth*iHeight;

        // Get pixel size in bytes
        iPixelSize=iBPP/8;

        for(Index=0;Index!=nPixels;Index++)  // For each pixel
        {
            bTemp=*bCur;      // Get Blue value
            *bCur=*(bCur+2);  // Swap red value into first position
            *(bCur+2)=bTemp;  // Write back blue to last position

            bCur+=iPixelSize; // Jump to next pixel
        }

    }


    void TGAImg::FlipImg() // Flips the image vertically (Why store images upside down?)
    {
        unsigned char bTemp;
        unsigned char *pLine1, *pLine2;
        int iLineLen,iIndex;

        iLineLen=iWidth*(iBPP/8);
        pLine1=pImage;
        pLine2=&pImage[iLineLen * (iHeight - 1)];

        for( ;pLine1<pLine2;pLine2-=(iLineLen*2))
        {
            for(iIndex=0;iIndex!=iLineLen;pLine1++,pLine2++,iIndex++)
            {
                bTemp=*pLine1;
                *pLine1=*pLine2;
                *pLine2=bTemp;       
            }
        } 

    }
}
